// SPDX-License-Identifier: Apache-2.0

package chisel3

import scala.collection.immutable.ListMap
import scala.collection.mutable.{ArrayBuffer, HashMap}
import scala.language.experimental.macros

import chisel3.internal._
import chisel3.internal.Builder._
import chisel3.internal.firrtl._
import chisel3.experimental.{BaseModule, SourceInfo, UnlocatableSourceInfo}
import chisel3.internal.sourceinfo.{InstTransform}
import chisel3.properties.Class
import chisel3.reflect.DataMirror
import _root_.firrtl.annotations.{IsModule, ModuleName, ModuleTarget}
import _root_.firrtl.AnnotationSeq
import chisel3.internal.plugin.autoNameRecursively
import chisel3.util.simpleClassName

object Module extends SourceInfoDoc {

  def apply[T <: BaseModule](bc: => T): T = macro InstTransform.apply[T]

  def do_apply[T <: BaseModule](bc: => T)(implicit sourceInfo: SourceInfo): T = {
    if (Builder.readyForModuleConstr) {
      throwException(
        "Error: Called Module() twice without instantiating a Module." +
          sourceInfo.makeMessage(" See " + _)
      )
    }
    Builder.readyForModuleConstr = true

    val parent = Builder.currentModule
    val parentWhenStack = Builder.whenStack

    val (saveClock, saveReset) = (Builder.currentClock, Builder.currentReset)
    val savePrefix = Builder.getPrefix
    Builder.clearPrefix()
    Builder.currentClock = None
    Builder.currentReset = None

    val module: T = bc // bc is actually evaluated here

    if (Builder.whenDepth != 0) {
      throwException("Internal Error! when() scope depth is != 0, this should have been caught!")
    }
    if (Builder.readyForModuleConstr) {
      throwException(
        "Error: attempted to instantiate a Module, but nothing happened. " +
          "This is probably due to rewrapping a Module instance with Module()." +
          sourceInfo.makeMessage(" See " + _)
      )
    }

    val componentOpt = module.generateComponent()
    for (component <- componentOpt) {
      Builder.components += component
    }

    Builder.currentModule = parent // Back to parent!
    Builder.whenStack = parentWhenStack
    Builder.currentClock = saveClock // Back to clock and reset scope
    Builder.currentReset = saveReset
    Builder.setPrefix(savePrefix)

    if (Builder.currentModule.isDefined && module._component.isDefined) {
      module match {
        case _: Class => throwException("Module() cannot be called on a Class. Please use Definition().")
        case _ => ()
      }

      val component = module._component.get
      pushCommand(DefInstance(sourceInfo, module, component.ports))
      module.initializeInParent()
    }
    module
  }

  def clock: Clock = Builder.forcedClock

  def clockOption: Option[Clock] = Builder.currentClock

  def reset: Reset = Builder.forcedReset

  def resetOption: Option[Reset] = Builder.currentReset

  def disable(implicit sourceInfo: SourceInfo): Disable =
    disableOption.getOrElse(throwException("Error: No implicit disable."))

  def disableOption(implicit sourceInfo: SourceInfo): Option[Disable] = {
    Builder.currentDisable match {
      case Disable.Never       => None
      case Disable.BeforeReset => hasBeenReset.map(x => autoNameRecursively("disable")(!x))
    }
  }

  private def hasBeenReset(implicit sourceInfo: SourceInfo): Option[Disable] = {
    (Builder.currentClock, Builder.currentReset) match {
      case (Some(clock), Some(reset)) =>
        val hasBeenReset = Module(new HasBeenResetIntrinsic)
        hasBeenReset.clock := clock
        hasBeenReset.reset := reset
        Some(new Disable(hasBeenReset.out))
      case _ => None
    }
  }

  def currentModule: Option[BaseModule] = Builder.currentModule

  private[chisel3] def do_pseudo_apply[T <: BaseModule](
    bc: => T
  )(
    implicit sourceInfo: SourceInfo
  ): T = {
    val parent = Builder.currentModule
    val module: T = bc // bc is actually evaluated here
    if (!parent.isEmpty) { Builder.currentModule = parent }

    module
  }

  private[chisel3] def assignCompatDir(data: Data): Unit =
    DataMirror
      .collectMembers(data) {
        case x: Element
            if x.specifiedDirection == SpecifiedDirection.Unspecified || x.specifiedDirection == SpecifiedDirection.Flip =>
          x
      }
      .foreach { x => x._assignCompatibilityExplicitDirection }

  object ResetType {

    sealed trait Type

    case object Default extends Type

    case object Uninferred extends Type

    case object Synchronous extends Type

    case object Asynchronous extends Type
  }
}


abstract class Module extends RawModule {

  def resetType: Module.ResetType.Type = Module.ResetType.Default

  final val clock: Clock = IO(Input(Clock()))(UnlocatableSourceInfo).suggestName("clock")
  final val reset: Reset = IO(Input(mkReset))(UnlocatableSourceInfo).suggestName("reset")

  private var _override_clock: Option[Clock] = None
  private var _override_reset: Option[Bool] = None
  @deprecated("Use withClock at Module instantiation", "Chisel 3.5")
  protected def override_clock: Option[Clock] = _override_clock
  @deprecated("Use withClock at Module instantiation", "Chisel 3.5")
  protected def override_reset: Option[Bool] = _override_reset
  @deprecated("Use withClock at Module instantiation", "Chisel 3.5")
  protected def override_clock_=(rhs: Option[Clock]): Unit = {
    _override_clock = rhs
  }
  @deprecated("Use withClock at Module instantiation", "Chisel 3.5")
  protected def override_reset_=(rhs: Option[Bool]): Unit = {
    _override_reset = rhs
  }

  private[chisel3] def mkReset: Reset = {
    resetType match {
      case Module.ResetType.Default => {
        val inferReset = (_parent.isDefined || Builder.inDefinition)
        if (inferReset) Reset() else Bool()
      }
      case Module.ResetType.Uninferred   => Reset()
      case Module.ResetType.Synchronous  => Bool()
      case Module.ResetType.Asynchronous => AsyncReset()
    }
  }

  Builder.currentClock = Some(clock)
  Builder.currentReset = Some(reset)
  Builder.clearPrefix()

  private[chisel3] override def initializeInParent(): Unit = {
    implicit val sourceInfo = UnlocatableSourceInfo

    super.initializeInParent()
    clock := _override_clock.getOrElse(Builder.forcedClock)
    reset := _override_reset.getOrElse(Builder.forcedReset)
  }
}

package internal {

  object BaseModule {

    import chisel3.experimental.hierarchy._

    private[chisel3] class ClonePorts(elts: (String, Data)*) extends Record {
      val elements = ListMap(elts.map { case (name, d) => name -> d.cloneTypeFull }: _*)
      def apply(field: String) = elements(field)
      override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type]
    }

    private[chisel3] def cloneIORecord(
      proto: BaseModule
    )(
      implicit sourceInfo: SourceInfo
    ): ClonePorts = {
      require(proto.isClosed, "Can't clone a module before module close")
      val cloneParent = Module(new ModuleClone(proto))
      require(proto.isClosed, "Can't clone a module before module close")
      require(cloneParent.getOptionRef.isEmpty, "Can't have ref set already!")
      val dataPorts = proto.getChiselPorts.collect { case (name, data: Data) => (name, data) }
      val clonePorts = proto match {
        case b: BlackBox =>
          new ClonePorts(dataPorts :+ ("io" -> b._io.get): _*)
        case _ => new ClonePorts(dataPorts: _*)
      }
      Module.assignCompatDir(clonePorts)
      clonePorts.bind(PortBinding(cloneParent))
      clonePorts.setAllParents(Some(cloneParent))
      cloneParent._portsRecord = clonePorts
      if (proto.isInstanceOf[Module]) {
        clonePorts("clock") := Module.clock
        clonePorts("reset") := Module.reset
      }
      clonePorts
    }
  }
}

package experimental {

  import chisel3.experimental.hierarchy.core.{IsInstantiable, Proto}
  import scala.annotation.nowarn

  object BaseModule {
    implicit class BaseModuleExtensions[T <: BaseModule](b: T)(implicit si: SourceInfo) {
      import chisel3.experimental.hierarchy.core.{Definition, Instance}
      def toInstance: Instance[T] = new Instance(Proto(b))
      def toDefinition: Definition[T] = {
        b.toDefinitionCalled = Some(si)
        new Definition(Proto(b))
      }
    }
  }

  @nowarn("msg=class Port") // delete when Port becomes private
  abstract class BaseModule extends HasId with IsInstantiable {
    _parent.foreach(_.addId(this))

    protected def _sourceInfo: SourceInfo = UnlocatableSourceInfo

    private[chisel3] final def _getSourceLocator: SourceInfo = _sourceInfo

    protected def _traitModuleDefinitionIdentifierProposal: Option[String] = None

    protected def _moduleDefinitionIdentifierProposal = {
      val baseName = _traitModuleDefinitionIdentifierProposal.getOrElse(this.getClass.getName)

      val filters: Seq[String => String] =
        Seq(((a: String) => raw"\$$+anon".r.replaceAllIn(a, "_Anon")) // Merge the "$$anon" name with previous name
        )

      filters
        .foldLeft(baseName) { case (str, filter) => filter(str) } // 1. Apply filters to baseName
        .split("\\.|\\$") // 2. Split string at '.' or '$'
        .filterNot(_.forall(_.isDigit)) // 3. Drop purely numeric names
        .last // 4. Use the last name
    }
    private[chisel3] def _definitionIdentifier = {
      val madeProposal = chisel3.naming.IdentifierProposer.makeProposal(this._moduleDefinitionIdentifierProposal)
      Builder.globalIdentifierNamespace.name(madeProposal)
    }

    final val definitionIdentifier = _definitionIdentifier
    this match {
      case _: PseudoModule =>
      case other =>
        if (!Builder.readyForModuleConstr) {
          throwException("Error: attempted to instantiate a Module without wrapping it in Module().")
        }
    }
    if (Builder.hasDynamicContext) {
      readyForModuleConstr = false

      Builder.currentModule = Some(this)
      Builder.whenStack = Nil
    }

    protected var _closed = false

    private[chisel3] def isClosed = _closed

    private[chisel3] var _whereIOCreationIsDisallowed: List[SourceInfo] = Nil

    private[chisel3] def isIOCreationAllowed = _whereIOCreationIsDisallowed.isEmpty

    private def disallowIOCreation()(implicit si: SourceInfo): Unit = {
      _whereIOCreationIsDisallowed = si +: _whereIOCreationIsDisallowed
    }

    private def allowIOCreation(): Unit = {
      if (_whereIOCreationIsDisallowed.nonEmpty) {
        _whereIOCreationIsDisallowed = _whereIOCreationIsDisallowed.tail
      }
    }

    def endIOCreation()(implicit si: SourceInfo): Unit = disallowIOCreation()

    private[chisel3] def disallowIO[T](thunk: => T)(implicit si: SourceInfo): T = {
      disallowIOCreation()
      val ret = thunk
      allowIOCreation()
      ret
    }

    private[chisel3] var toDefinitionCalled:  Option[SourceInfo] = None
    private[chisel3] var modulePortsAskedFor: Option[SourceInfo] = None

    private[chisel3] def isFullyClosed = fullyClosedErrorMessages.nonEmpty
    private[chisel3] def fullyClosedErrorMessages: Iterable[(SourceInfo, String)] = {
      toDefinitionCalled.map(si =>
        (si, s"Calling .toDefinition fully closes ${name}, but it is later bored through!")
      ) ++
        modulePortsAskedFor.map(si =>
          (si, s"Reflecting on all io's fully closes ${name}, but it is later bored through!")
        )
    }

    private[chisel3] val _namespace = Namespace.empty

    private[chisel3] val _ids = ArrayBuffer[HasId]()

    private[chisel3] def addId(d: HasId): Unit = {
      if (Builder.aspectModule(this).isDefined) {
        aspectModule(this).get.addId(d)
      } else {
        require(!_closed, "Can't write to module after module close")
        _ids += d
      }
    }

    private[chisel3] def _lastId: Long = _ids.last match {
      case mod: BaseModule => mod._lastId
      case agg: Aggregate  =>
        getRecursiveFields.lazily(agg, "").map(_._1._id).max
      case other => other._id
    }

    private[chisel3] def getIds: Iterable[HasId] = {
      require(_closed, "Can't get ids before module close")
      _ids
    }

    private val _ports = new ArrayBuffer[(Data, SourceInfo)]()

    protected[chisel3] def getModulePorts: Seq[Data] = {
      require(_closed, "Can't get ports before module close")
      _ports.iterator.collect { case (d: Data, _) => d }.toSeq
    }

    private[chisel3] def getModulePortsAndLocators: Seq[(Data, SourceInfo)] = {
      require(_closed, "Can't get ports before module close")
      _ports.toSeq
    }

    protected def portsContains(elem: Data): Boolean = {
      _ports.exists { port => port._1 == elem }
    }

    private[chisel3] def findPort(name: String): Option[Data] =
      _ports.collectFirst { case (data, _) if data.seedOpt.contains(name) => data }

    protected def portsSize: Int = _ports.size

    private[chisel3] def generateComponent(): Option[Component]

    private[chisel3] def initializeInParent(): Unit

    private[chisel3] def namePorts(): Unit = {
      for ((port, source) <- getModulePortsAndLocators) {
        port._computeName(None) match {
          case Some(name) =>
            if (_namespace.contains(name)) {
              Builder.error(
                s"""Unable to name port $port to "$name" in $this,""" +
                  s" name is already taken by another port! ${source.makeMessage(x => x)}"
              )(UnlocatableSourceInfo)
            }
            port.setRef(ModuleIO(this, _namespace.name(name)))
          case None =>
            Builder.error(
              s"Unable to name port $port in $this, " +
                s"try making it a public field of the Module ${source.makeMessage(x => x)}"
            )(UnlocatableSourceInfo)
            port.setRef(ModuleIO(this, "<UNNAMED>"))
        }
      }
    }


    def desiredName: String = simpleClassName(this.getClass)

    final lazy val name =
      try {
        this match {
          case _: PseudoModule => desiredName
          case _ => Builder.globalNamespace.name(desiredName)
        }
      } catch {
        case e: NullPointerException =>
          throwException(
            s"Error: desiredName of ${this.getClass.getName} is null. Did you evaluate 'name' before all values needed by desiredName were available?",
            e
          )
        case t: Throwable => throw t
      }

    final def toNamed: ModuleName = ModuleTarget(this.circuitName, this.name).toNamed

    final def toTarget: ModuleTarget = this match {
      case m: experimental.hierarchy.InstanceClone[_] =>
        throwException(s"Internal Error! It's not legal to call .toTarget on an InstanceClone. $m")
      case m: experimental.hierarchy.DefinitionClone[_] =>
        throwException(s"Internal Error! It's not legal to call .toTarget on an DefinitionClone. $m")
      case _ => ModuleTarget(this.circuitName, this.name)
    }

    private[chisel3] def getTarget: IsModule = this match {
      case m: experimental.hierarchy.InstanceClone[_] if m._parent.nonEmpty =>
        m._parent.get.getTarget.instOf(instanceName, name)
      case m: experimental.hierarchy.ModuleClone[_] if m._madeFromDefinition =>
        m._parent.get.getTarget.instOf(instanceName, name)
      case m: experimental.hierarchy.DefinitionClone[_] if m._circuit.nonEmpty =>
        ModuleTarget(this._circuit.get.circuitName, this.name)
      case _ => this.toTarget
    }

    final def toAbsoluteTarget: IsModule = {
      _parent match {
        case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, name)
        case None         =>
          if (this == ViewParent) ViewParent.absoluteTarget else getTarget
      }
    }

    final def toRelativeTarget(root: Option[BaseModule]): IsModule = {
      if (root.contains(this)) getTarget
      else if (this == ViewParent) ViewParent.absoluteTarget
      else
        (root, _parent) match {
          case (_, Some(parent)) => parent.toRelativeTarget(root).instOf(this.instanceName, name)
          case (Some(definedRoot), None) =>
            throwException(s"Requested .toRelativeTarget relative to ${definedRoot.name}, but it is not an ancestor")
          case (None, None) => getTarget
        }
    }

    private[chisel3] def getChiselPorts(implicit si: SourceInfo): Seq[(String, Data)] = {
      require(_closed, "Can't get ports before module close")
      modulePortsAskedFor = Some(si) // super-lock down the module
      (_component.get.ports ++ _component.get.secretPorts).map { port =>
        (port.id.getRef.asInstanceOf[ModuleIO].name, port.id)
      }
    }

    protected def _bindIoInPlace(iodef: Data)(implicit sourceInfo: SourceInfo): Unit = {

      iodef match {
        case (data: Data) => Module.assignCompatDir(data)
        case _ => ()
      }

      iodef.bind(PortBinding(this))
      _ports += iodef -> sourceInfo
    }

    private[chisel3] def bindIoInPlace(
      iodef: Data
    )(
      implicit sourceInfo: SourceInfo
    ): Unit = _bindIoInPlace(iodef)

    private[chisel3] def createSecretIO[A <: Data](data: => A)(implicit sourceInfo: SourceInfo): A = {
      val iodef = data
      internal.requireIsChiselType(iodef, "io type")
      require(!isFullyClosed, "Cannot create secret ports if module is fully closed")

      Module.assignCompatDir(iodef)
      iodef.bind(internal.SecretPortBinding(this), iodef.specifiedDirection)
      iodef
    }

    private[chisel3] val secretPorts: ArrayBuffer[Port] = ArrayBuffer.empty

    private[chisel3] def addSecretIO[A <: Data](iodef: A)(implicit sourceInfo: SourceInfo): A = {
      val name = iodef._computeName(None).getOrElse("secret")
      iodef.setRef(ModuleIO(this, _namespace.name(name)))
      val newPort = new Port(iodef, iodef.specifiedDirection, sourceInfo)
      if (_closed) {
        _component.get.secretPorts += newPort
      } else secretPorts += newPort
      iodef
    }

    protected def IO[T <: Data](iodef: => T)(implicit sourceInfo: SourceInfo): T = {
      chisel3.IO.apply(iodef)
    }


    private[chisel3] var _component: Option[Component] = None

    override def instanceName: String =
      if (_parent == None) name
      else
        _component match {
          case None    => getRef.name
          case Some(c) => getRef.fullName(c)
        }

  }
}
